
需求描述
产品添加页面，需要选择车型。在bootStrap的modal上弹出子modal来使用。车型一共有4级目录。要使用目录树。然后分活动和商品两种，需要能够通过不通参数来调用该组件。车型品牌要使用字母导航。

&nbsp;
技术实现
数据都是后端传json过来，我们ajax获取然后操作。由于车型总数据有几万条以上，不可能一次性请求过来。这里我们使用异步的方式，每点击一次目录节点，加载它的下一级。这里我们用两个参数来控制活动和商品的不同加载。_showPrice和opened后端传过来的第一级数据是车型品牌，其中有首字母的字段。字母导航的初始化，就是把这个数据用firstWord属性来排序，然后忽略掉其他首字母相同的元素。对于活动类型，需要返回所勾选的最低一级的数据。（勾选奥迪和奥迪A6，则只返回A6的意思），这里用了整整4层循环。不过它是根据是否有checked来遍历的，速度不慢。
&nbsp;

  1 /**
  2  * Created by nuenfeng on 2016/5/23.
  3  * 车型选择组件
  4  * 参数：
  5  * showPrice 是否要输入价格（不输入价格的从品牌开始就有选项框，没有全选功能）
  6  * params 外部传入的对象
  7  * callback 回调函数
  8  */
  9 (function () {
 10     var uriCarBrand = global.url.carBrandList;
 11     //var uri = uriCarBrand.url;
 12     var opened = false; //当前页面是否打开过本组件
 13     var _callback;      //回调函数
 14     var requestParams;  //请求时要使用的参数
 15     var _showPrice;     //是否要输入价格
 16     var lastShowPrice;  //前一次打开状态
 17     var charNavArr;     //字母导航数组
 18 
 19     function CarTree(showPrice, params, callback) {
 20         // 没打开过，初始化； 打开过，直接显示modal
 21         requestParams = params;
 22         _showPrice = showPrice;
 23         _callback = callback;
 24 
 25         if (!opened || lastShowPrice != showPrice) {
 26             this.init();
 27             opened = true;
 28             lastShowPrice = showPrice;
 29         } else {
 30             $('#zc-sub-modal').modal('show');
 31         }
 32     }
 33 
 34     CarTree.prototype.init = function () {
 35 
 36         msjcTools.addSubModal();
 37         //设置大模态框
 38         $('#zc-sub-modal').addClass("bs-example-modal-lg");
 39         $('#zc-sub-modal .modal-dialog').addClass("modal-lg");
 40 
 41         var str = '<form id="info-form" data-parsley-validate class="form-horizontal form-label-left">';
 42         str += '<ul id="resourceId" class="treeview-gray">'
 43         str += '<li id="cb_0"><span>汽车品牌选择</span>';
 44         str += "</li>"
 45         str += '</ul>'
 46         str += '</form>';
 47 
 48         var objId = 'cb_0';
 49         var carBrandId = 0;
 50         loadSubMenu(objId, carBrandId, 1);  //1 表示第一次加载，用于加载成功后判断时候要初始化字母导航
 51 
 52         $('#zc-sub-modal-body').html(str);
 53         $('#zc-sub-modal').modal({
 54             keyboard: false,
 55             show: true
 56         });
 57 
 58         // 点击保存事件
 59         $('#zc-sub-modal .modal-footer .btn.btn-primary').unbind().bind("click", function () {
 60             save();
 61         });
 62 
 63 
 64         //$("#resourceId").find("input[type=checkbox]").unbind().bind("click",function(){
 65         //    if($(this).is(':checked')==true){//选中 则其上层节点全部展开并选中
 66         //        //上级选中
 67         //        $(this).parents("li").each(function(){
 68         //            $(this).find("input[type=checkbox]:first").attr("checked",true)
 69         //        });
 70         //    } else {
 71         //        //下级取消选中
 72         //        $(this).siblings("ul").find("input[type=checkbox]").each(function(){
 73         //            $(this).removeAttr("checked");
 74         //        });
 75         //    }
 76         //});
 77 
 78 
 79         //隐藏子窗口后 保持父窗口的滚动
 80         $("#zc-sub-modal").on("hidden.bs.modal", function () {
 81             $('body').addClass('modal-open')
 82         });
 83     }
 84 
 85     CarTree.prototype.empty = function () {
 86         opened = false;
 87         console.log('empty me');
 88     }
 89 
 90     //加载子菜单
 91     var loadSubMenu = function (objId, carBrandId, times) {
 92 
 93         requestParams.brandId = carBrandId;
 94         executeAjax(global.url.carBrandList, requestParams, function (data) {
 95             // 给data风骚地排个序
 96             data.sort(keysrt("firstWord"));
 97 
 98             var menuHtml = "<ul>";
 99             for (var index in data) {
100                 var menu = data[index];
101                 menuHtml += '<li id="cb_' + menu.carBrandId + '" value="' + menu.carBrandId + '" brand="' + menu.brand + '">';
102 
103                 // 带价格的树
104                 if (_showPrice) {
105                     // 最后一级，添加选项框
106                     if (menu.level > 3) {
107                         menuHtml += '<input type="checkbox" name="resourceIds" value="' + menu.carBrandId + '" />';
108                     }
109                     menuHtml += '<span>' + menu.name + '</span>';
110 
111                     // 最后一级，添加输入框
112                     if (menu.level == 4) {
113                         menuHtml += '<input type="text" maxlength="9">';
114                     }
115                 } else { // 不带价格的树
116                     menuHtml += '<input type="checkbox" name="resourceIds" value="' + menu.carBrandId + '" />';
117                     menuHtml += '<span>' + menu.name + '</span>';
118                 }
119 
120                 menuHtml += "</li>";
121             }
122             menuHtml += "</ul>";
123             $('#' + objId).append(menuHtml);
124             $('#' + objId).attr('data-load', 'loaded');
125 
126             //汽车类型第一级加载完成后，初始化字符导航
127             charNavArr = [];
128             var fwdLast = '';    //上一次的首字母
129 
130             for (var i in data) {
131                 var cobjTemp = {};
132                 if (fwdLast != data[i].firstWord) {
133                     fwdLast = data[i].firstWord;
134                     cobjTemp.firstWord = fwdLast;
135                     cobjTemp.targetId = 'cb_'+data[i].carBrandId;
136                     charNavArr.push(cobjTemp);
137                 }
138             }
139             if (times == 1) {
140                 initCharNav();
141                 // 点击保存事件
142                 $('.charNavSaveBtn').unbind().bind("click", function () {
143                     save();
144                 });
145             }
146         });
147 
148     }
149 
150     // 此处是风骚的数组对象排序
151     var keysrt = function (propertyName) {
152         return function (object1, object2) {
153             var value1 = object1[propertyName];
154             var value2 = object2[propertyName];
155             if (value2 < value1) {
156                 return 1;
157             }
158             else if (value2 > value1) {
159                 return -1;
160             }
161             else {
162                 return 0;
163             }
164         }
165     }
166 
167     // 保存事件
168     var save = function(){
169         // 确认后，执行回调函数
170         if (_showPrice) {
171             var res = getPriceResult();
172             if (res.status) {
173                 _callback(res.data);
174             } else {
175                 alert(res.error);
176                 return;
177             }
178         } else {
179             _callback(getNopriceResult());
180         }
181         //返回数据，然后隐藏
182         $('#zc-sub-modal').modal('hide');
183     }
184 
185     // 设置字符导航初始化
186     var initCharNav = function () {
187         var charNavHtml = '<ul id="charNavBar" class="charNavBar pagination">';
188         for (var i in charNavArr) {
189             charNavHtml += '<li><a href="#'+charNavArr[i].targetId+'">'+charNavArr[i].firstWord+'</a></li>';
190         }
191         charNavHtml += '<li><a class="modalGoTop">↑</a></li>';
192         charNavHtml += '<button type="button" class="btn btn-primary charNavSaveBtn">保存</button>';
193         charNavHtml += '</ul>';
194 
195         $('#zc-sub-modal').append(charNavHtml);
196 
197         $('.modalGoTop').on('click', function(e){
198             $('#zc-sub-modal').animate({scrollTop: 0}, 500);
199         });
200     }
201 
202     // 统计带价格的返回数据
203     var getPriceResult = function () {
204         var result = {
205             status : true,
206             data : [],
207             error : ''
208         };
209         var liTemp;
210         var objTemp;
211         $('.treeview-gray input:checkbox:checked').each(function (i) {
212             liTemp = $(this).parent('li');
213             objTemp = {};
214             objTemp.carBrandId = liTemp.attr('value');
215             objTemp.brand = liTemp.attr('brand');
216             objTemp.carBrandName = liTemp.find('span').text();
217             objTemp.unitPrice = liTemp.find('input:text').val();
218 
219             // 如果价格没有输入，返回保存失败，并返回没有输入的carBrandName
220             if(objTemp.unitPrice == '') {
221                 result.status = false;
222                 result.error = '请输入 ' + objTemp.carBrandName + ' 的价格！';
223                 return result;
224             }
225             result.data.push(objTemp);
226         });
227         return result;
228     }
229 
230     // 统计不带价格的返回数据
231     var getNopriceResult = function () {
232         var result = [];
233         var liTemp;
234         var objTemp;
235         var flag1;
236         var flag2;
237         var flag3;
238         var flag4;
239         var level2Name;
240 
241         // 遍历4层
242         $('#cb_0').children().children('li').children('input:checkbox').each(function (i1) {
243             if ($(this).is(':checked')) {
244                 flag1 = true;
245             } else {
246                 flag1 = false;
247             }
248             $(this).parent().children().children('li').children('input:checkbox').each(function (i2) {
249                 if ($(this).is(':checked')) {
250                     flag1 = false;
251                     flag2 = true;
252                 } else {
253                     flag2 = false;
254                 }
255                 // 获取第二级的名字，给第三级使用
256                 liTemp = $(this).parent('li');
257                 level2Name = liTemp.children('span').text();
258                 $(this).parent().children().children('li').children('input:checkbox').each(function (i3) {
259                     if ($(this).is(':checked')) {
260                         flag1 = false;
261                         flag2 = false;
262                         flag3 = true;
263                     } else {
264                         flag3 = false;
265                     }
266                     $(this).parent().children().children('li').children('input:checkbox').each(function (i4) {
267                         if ($(this).is(':checked')) {
268                             flag1 = false;
269                             flag2 = false;
270                             flag3 = false;
271                             flag4 = true;
272                         } else {
273                             flag4 = false;
274                         }
275                         if (flag4) {
276                             liTemp = $(this).parent('li');
277                             objTemp = {};
278                             objTemp.carBrandId = liTemp.attr('value');
279                             objTemp.brand = liTemp.attr('brand');
280                             //objTemp.carBrandName = liTemp.children('span').text();
281                             objTemp.carBrandName = objTemp.brand + ' ' + liTemp.children('span').text();
282                             result.push(objTemp);
283                         }
284                     });
285                     if (flag3) {
286                         liTemp = $(this).parent('li');
287                         objTemp = {};
288                         objTemp.carBrandId = liTemp.attr('value');
289                         objTemp.brand = liTemp.attr('brand');
290                         //objTemp.carBrandName = liTemp.children('span').text();
291                         objTemp.carBrandName = objTemp.brand + ' ' + level2Name + ' ' + liTemp.children('span').text();
292                         result.push(objTemp);
293                     }
294                 });
295                 if (flag2) {
296                     //liTemp = $(this).parent('li');
297                     objTemp = {};
298                     objTemp.carBrandId = liTemp.attr('value');
299                     objTemp.brand = liTemp.attr('brand');
300                     //objTemp.carBrandName = objTemp.brand + liTemp.children('span').text();
301                     objTemp.carBrandName = objTemp.brand + ' ' + liTemp.children('span').text();
302                     result.push(objTemp);
303                 }
304             });
305             if (flag1) {
306                 liTemp = $(this).parent('li');
307                 objTemp = {};
308                 objTemp.carBrandId = liTemp.attr('value');
309                 objTemp.brand = liTemp.attr('brand');
310                 objTemp.carBrandName = liTemp.children('span').text();
311                 result.push(objTemp);
312             }
313         });
314 
315         return result;
316     }
317 
318 
319     // 给目录树绑定点击事件
320     $(document).on('click', '#resourceId li', function (e) {
321 
322 
323         e.stopPropagation();
324 
325         if ($(this).attr('open')) {
326             $(this).removeAttr('open');
327             $(this).children('ul').hide();
328         } else {
329             $(this).attr('open', 'opened');
330             $(this).children('ul').show();
331         }
332         var objId = $(this).attr('id');
333         var carBrandId = $(this).attr('value');
334         //加载过的不执行
335         if ($(this).attr('data-load')) {
336             return;
337         }
338         loadSubMenu(objId, carBrandId);
339     });
340 
341     // 点击多选框时候不下拉
342     $(document).on('click', 'input[type="checkbox"]', function (e) {
343         e.stopPropagation();
344     });
345 
346     window.CarTree = CarTree;
347 }());

&nbsp;
调用方法：

carTree = new CarTree(false, {}, function (data) {
    console.log(data);
 });

&nbsp;
